#include "glx_context.h"
#include <stdlib.h> 
#include <stdio.h>
#include <string.h>


#include <GL/glext.h>
#if GL_GLEXT_VERSION < 42
#error "I need a newer <GL/glext.h>. Please download it from http://www.opengl.org/registry/ABI/"
#endif

// Helper to check for extension string presence.  Adapted from:
//   http://www.opengl.org/resources/features/OGLextensions/
static bool isExtensionSupported(const char *extList, const char *extension)
 
{
 
  const char *start;
  const char *where, *terminator;
 
  /* Extension names should not have spaces. */
  where = strchr(extension, ' ');
  if ( where || *extension == '\0' )
    return false;
 
  /* It takes a bit of care to be fool-proof about parsing the
     OpenGL extensions string. Don't be fooled by sub-strings,
     etc. */
  for ( start = extList; ; ) {
    where = strstr( start, extension );
 
    if ( !where )
      break;
 
    terminator = where + strlen( extension );
 
    if ( where == start || *(where - 1) == ' ' )
      if ( *terminator == ' ' || *terminator == '\0' )
        return true;
 
    start = terminator;
  }
 
  return false;
}

// Get a matching FB config
static int Prefferred_visual_attribs[] =
{
  GLX_X_RENDERABLE    , True,
  GLX_DRAWABLE_TYPE   , GLX_WINDOW_BIT,
  GLX_RENDER_TYPE     , GLX_RGBA_BIT,
  GLX_X_VISUAL_TYPE   , GLX_TRUE_COLOR,
  GLX_RED_SIZE        , 8,
  GLX_GREEN_SIZE      , 8,
  GLX_BLUE_SIZE       , 8,
  GLX_ALPHA_SIZE      , 8,
  GLX_DEPTH_SIZE      , 24,
  GLX_STENCIL_SIZE    , 8,
  GLX_DOUBLEBUFFER    , True,
  //GLX_SAMPLE_BUFFERS  , 1,
  //GLX_SAMPLES         , 4,
  None
};
const char* Engine_Title = "Humble Game Engine";
const char* Engine_Class = "humbleGLCore";

GLX_Video_Context::GLX_Video_Context()
{
    m_hdisplay = NULL;
    m_hwindow = 0;
    m_width = 800;
    m_height = 600;
    m_bfullscreen = false;
    setWindowName( sizeof(Engine_Title),(char*) Engine_Title);
    setWindowGroup(sizeof(Engine_Class),(char*) Engine_Class);

    visual_attribs = new int[sizeof(Prefferred_visual_attribs)];
    memcpy(visual_attribs,Prefferred_visual_attribs,sizeof(Prefferred_visual_attribs));
 
}
GLX_Video_Context::~GLX_Video_Context()
{
   
}

GLX_Video_Context *CurrentContext;

static int ctxErrorHandler( Display *dpy, XErrorEvent *ev )
{
    return CurrentContext->ErrorHandler(dpy,ev);
}
/******************************************************************************
 * function: GLX_Video_Context::initialize()
 * Description:
 *     Initialize X11 signals and verify GLX version.
 *****************************************************************************/
bool GLX_Video_Context::initialize()
{
    // Run a few Initialization tests.

    m_hdisplay = XOpenDisplay(0);

    if ( !m_hdisplay )
    {
        printf( "Failed to open X display\n" );
        return false;
    }

    int glx_major, glx_minor;

    // FBConfigs were added in GLX version 1.3.
    if ( !glXQueryVersion( m_hdisplay, &glx_major, &glx_minor ) || 
       ( ( glx_major == 1 ) && ( glx_minor < 3 ) ) || ( glx_major < 1 ) )
    {
        printf( "Invalid GLX version" );
        return false;
    }

    printf( "Getting matching framebuffer configs\n" );
    int fbcount;
    GLXFBConfig *fbc = glXChooseFBConfig( m_hdisplay, DefaultScreen( m_hdisplay ), 
                                        visual_attribs, &fbcount );
    if ( !fbc )
    {
        printf( "Failed to retrieve a framebuffer config\n" );
        return false;
    }
    printf( "Found %d matching FB configs.\n", fbcount );

    // Pick the FB config/visual with the most samples per pixel
    printf( "Getting XVisualInfos\n" );
    int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = 999;

    int i;
    for ( i = 0; i < fbcount; i++ )
    {
        XVisualInfo *vi = glXGetVisualFromFBConfig( m_hdisplay, fbc[i] );
        if ( vi )
        {
          int samp_buf, samples;
          glXGetFBConfigAttrib( m_hdisplay, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf );
          glXGetFBConfigAttrib( m_hdisplay, fbc[i], GLX_SAMPLES       , &samples  );

          printf( "  Matching fbconfig %d, visual ID 0x%2x: SAMPLE_BUFFERS = %d,"
                  " SAMPLES = %d\n", 
                  i, (unsigned int)vi -> visualid, samp_buf, samples );

          if ( best_fbc < 0 || samp_buf && samples > best_num_samp )
            best_fbc = i, best_num_samp = samples;
          if ( worst_fbc < 0 || !samp_buf || samples < worst_num_samp )
            worst_fbc = i, worst_num_samp = samples;
        }
        XFree( vi );
    }

    bestFbc = fbc[ best_fbc ];

    // Be sure to free the FBConfig list allocated by glXChooseFBConfig()
    XFree( fbc );

    // Get the default screen's GLX extension list
    glxExts = (char*)glXQueryExtensionsString( m_hdisplay,
                                                  DefaultScreen( m_hdisplay ) );

    if ( !isExtensionSupported( glxExts, "GLX_ARB_create_context" ))
    {
        // NOTE: It is not necessary to create or make current to a context before
        // calling glXGetProcAddressARB
        glXCreateContextAttribsARB = 0;
        glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)
               glXGetProcAddressARB( (const GLubyte *) "glXCreateContextAttribsARB" );
      if(!glXCreateContextAttribsARB )
      {
        printf( "glXCreateContextAttribsARB() not found"
                " ... using old-style GLX context\n" );
        m_GLX_Context = glXCreateNewContext( m_hdisplay, bestFbc, GLX_RGBA_TYPE, 0, True );
      }
    }
    return true;
}
bool GLX_Video_Context::CreateWindow()
{
    if(m_hwindow)
    {  
        return false;
    }
    // Get a visual
    XVisualInfo *vi = glXGetVisualFromFBConfig( m_hdisplay, bestFbc );
    printf( "Chosen visual ID = 0x%x\n", (unsigned int)vi->visualid );

    printf( "Creating colormap\n" );
    XSetWindowAttributes swa;
    Colormap cmap;
    swa.colormap = cmap = XCreateColormap( m_hdisplay,
                                         RootWindow( m_hdisplay, vi->screen ), 
                                         vi->visual, AllocNone );
    swa.background_pixmap = None ;
    swa.border_pixel      = 0;
    swa.event_mask        = StructureNotifyMask;

    printf( "Creating window\n" );
    m_hwindow = XCreateWindow( m_hdisplay, RootWindow( m_hdisplay, vi->screen ), 
                              0, 0, m_width, m_height, 0, vi->depth, InputOutput, 
                              vi->visual, 
                              CWBorderPixel|CWColormap|CWEventMask, &swa );
    if ( !m_hwindow )
    {
        printf( "Failed to create window.\n" );
        return false;
    }

    // Done with the visual info data
    XFree( vi );

    XStoreName( m_hdisplay, m_hwindow, m_WindowName );

    printf( "Mapping window\n" );
    XMapWindow( m_hdisplay, m_hwindow );

    m_GLX_Context = 0;

    // Install an X error handler so the application won't exit if GL 3.0
    // context allocation fails.
    //
    // Note this error handler is global.  All display connections in all threads
    // of a process use the same error handler, so be sure to guard against other
    // threads issuing X commands while this code is running.
    m_ErrorOccurred = false;
    int (*oldHandler)(Display*, XErrorEvent*) =
      XSetErrorHandler(&ctxErrorHandler);

    // Check for the GLX_ARB_create_context extension string and the function.
    // If either is not present, use GLX 1.3 context creation method.
    if ( !isExtensionSupported( glxExts, "GLX_ARB_create_context" ) ||
       !glXCreateContextAttribsARB )
    {
        printf( "glXCreateContextAttribsARB() not found"
                " ... using old-style GLX context\n" );
        m_GLX_Context = glXCreateNewContext( m_hdisplay, bestFbc, GLX_RGBA_TYPE, 0, True );
    }

    // If it does, try to get a GL 3.0 context!
    else
    {
        int context_attribs[] =
          {
            GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
            GLX_CONTEXT_MINOR_VERSION_ARB, 0,
            //GLX_CONTEXT_FLAGS_ARB        , GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
            None
          };

        printf( "Creating context\n" );
        m_GLX_Context = glXCreateContextAttribsARB( m_hdisplay, bestFbc, 0,
                                          True, context_attribs );

        // Sync to ensure any errors generated are processed.
        XSync( m_hdisplay, False );
        if ( !m_ErrorOccurred && m_GLX_Context )
          printf( "Created GL 3.0 context\n" );
        else
        {
          // Couldn't create GL 3.0 context.  Fall back to old-style 2.x context.
          // When a context version below 3.0 is requested, implementations will
          // return the newest context version compatible with OpenGL versions less
          // than version 3.0.
          // GLX_CONTEXT_MAJOR_VERSION_ARB = 1
          context_attribs[1] = 1;
          // GLX_CONTEXT_MINOR_VERSION_ARB = 0
          context_attribs[3] = 0;

          m_ErrorOccurred = false;

          printf( "Failed to create GL 3.0 context"
                  " ... using old-style GLX context\n" );
          m_GLX_Context = glXCreateContextAttribsARB( m_hdisplay, bestFbc, 0, 
                                            True, context_attribs );
        }
    }

    // Sync to ensure any errors generated are processed.
    XSync( m_hdisplay, False );

    // Restore the original error handler
    XSetErrorHandler( oldHandler );

    if (m_ErrorOccurred || !m_GLX_Context )
    {
    printf( "Failed to create an OpenGL context\n" );
    exit(1);
    }

    // Verifying that context is a direct context
    if ( ! glXIsDirect ( m_hdisplay, m_GLX_Context ) )
    {
    printf( "Indirect GLX rendering context obtained\n" );
    }
    else
    {
    printf( "Direct GLX rendering context obtained\n" );
    }

    printf( "Making context current\n" );
    glXMakeCurrent( m_hdisplay, m_hwindow, m_GLX_Context );
    return true;
}
bool GLX_Video_Context::DestroyWindow()
{
  XDestroyWindow( m_hdisplay, m_hwindow );
  XFreeColormap( m_hdisplay, m_hcmap );
}
bool GLX_Video_Context::setResolution(int height, int width)
{
    // Todo: Add randR calls.
    return true;
}
bool GLX_Video_Context::setFullScreen(bool fullscreen)
{
    // Todo: Add randR calls.
    return true;
}

bool GLX_Video_Context::isFullscreen()
{
    // Todo: Add randR calls.
    return true;
}
void GLX_Video_Context::setWindowName(size_t s_name, char *name)
{
    if( s_name <= sizeof(m_WindowName))
    {
        memcpy(m_WindowName,name,s_name);
    } else {
        memcpy(m_WindowName,name,49);
        m_WindowName[49] = '\0';
    }
    // Todo: Add randR calls.
    return;
}
void GLX_Video_Context::setWindowGroup(size_t s_name, char *name)
{
    if( s_name <= sizeof(m_ClassName))
    {
        memcpy(m_ClassName,name,s_name);
    } else {
        memcpy(m_ClassName,name,49);
        m_ClassName[49] = '\0';
    }
    // Todo: Add randR calls.
    return;
}
void GLX_Video_Context::flipPages()
{
    glXSwapBuffers ( m_hdisplay, m_hwindow );
}
